{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "783725f0-857b-4ca6-800a-5837766c8ddc",
   "metadata": {},
   "source": [
    "# CW-Husky SAD Triggering\n",
    "\n",
    "Sum-of-Absolute Differences triggering on Husky is quite a bit different from how it is on CW-Pro.\n",
    "\n",
    "The API has changed, the way that SAD is computed has changed, and there are a lot more options. But fear not, we've also tried to make it as easy as possible to use.\n",
    "\n",
    "First, a reference trace must be established; this is very target and compiler-dependent, so we'll pull in pre-compiled AES firmware for the SAM4S target.\n",
    "\n",
    "**SAD can absolutely be used for different targets, but for learning it's best to stick with this.**\n",
    "\n",
    "If you're using a different target, you'll have to select a \"good\" SAD reference, which can be a bit of a black art. You should try! But only after you've learned the basics."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ab4a5eb-f9d6-411c-9d56-344fb8599a8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "PLATFORM = 'CW308_SAM4S'\n",
    "SS_VER = \"SS_VER_1_1\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e89ee96b-d54d-468a-b5bb-e9a3baebbf99",
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "scope = cw.scope()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b1a17b8-590c-499f-a6f8-c195cded9dda",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "%run ../../Setup_Scripts/Setup_Generic.ipynb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7db4ec3d-5d9e-43fa-80d5-673aa2be7bd2",
   "metadata": {},
   "outputs": [],
   "source": [
    "cw.program_target(scope, prog, \"../../../firmware/mcu/simpleserial-trace/simpleserial-trace-{}.hex\".format(PLATFORM))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22adfb3f-329d-4d76-8617-423602cea2f0",
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_target(scope)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58d61232-eb02-4d14-b515-4683aa99398b",
   "metadata": {},
   "source": [
    "Let's start with a regular TIO4-triggered AES capture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd3f44e2-6d07-40d7-a554-f78b41d9b27a",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trigger.module = 'basic'\n",
    "scope.trigger.triggers = 'tio4'\n",
    "\n",
    "scope.adc.samples = 35000\n",
    "scope.adc.presamples = 0\n",
    "scope.adc.segments = 1\n",
    "scope.adc.bits_per_sample = 8  # SAD is done at 8 bits per sample\n",
    "\n",
    "scope.gain.db = 22"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf57e4b6-ce22-45b7-97bc-c0f1d47e15d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "reftrace = cw.capture_trace(scope, target, bytearray(16), bytearray(16), as_int=True)\n",
    "assert scope.adc.trig_count == 31864, \"Unexpected trigger count. Are you running the correct firmware?\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd03d394-50b0-4602-8c05-ae1698da8f2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "refstart = 17372\n",
    "\n",
    "from bokeh.plotting import figure, show\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span\n",
    "\n",
    "output_notebook()\n",
    "p = figure(width=1800, tools='pan, box_zoom, hover, reset, save')\n",
    "\n",
    "xrange = list(range(len(reftrace.wave)))\n",
    "p.line(xrange, reftrace.wave)\n",
    "p.renderers.extend([Span(location=refstart, dimension='height', line_color='black', line_width=2)])\n",
    "p.renderers.extend([Span(location=refstart+scope.SAD.sad_reference_length, dimension='height', line_color='black', line_width=2)])\n",
    "\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "471a204c-5cc0-4e27-b52b-3881b2e1a2a5",
   "metadata": {},
   "source": [
    "With this target, the AES rounds should be fairly obvious. Our goal will be to have a SAD reference that matches each of the 10 AES rounds,\n",
    "\n",
    "We've pre-selected something that looks like a good reference starting at sample 17372; the vertical black lines in the plot above show the SAD reference that we'll use below."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd800654-3136-454f-8e04-cc32e645bc12",
   "metadata": {},
   "source": [
    "# Model time\n",
    "\n",
    "It's often easier to check if we have good SAD parameters by running software SAD on our trace. ChipWhisperer includes a software model of its hardware SAD implementation.\n",
    "\n",
    "To make it easier to go back-and-forth between the software model and Husky's actual FPGA implementation, the software SAD model is configured via the `scope.SAD` object; once the model is configured to our liking, the hardware will be ready to go!\n",
    "\n",
    "Let's configure `scope.SAD`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c924f73-3d8d-4de0-8dd3-15c728182ab1",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.reference = reftrace.wave[refstart:]\n",
    "scope.SAD.threshold = 20\n",
    "scope.SAD.interval_threshold = 20\n",
    "scope.SAD.multiple_triggers = True\n",
    "scope.SAD.emode = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88820dae-79dc-4ce2-8ffa-824e39a1c5a3",
   "metadata": {},
   "source": [
    "All of these parameters will be explained later; one important setting that we'll explain here is `scope.SAD.multiple_triggers`:\n",
    "\n",
    "- If `scope.SAD.multiple_triggers` is set, then once the scope is armed, a trigger will be issued whenever the SAD threshold is met.\n",
    "- If `scope.SAD.multiple_triggers` is not set, then no triggers are issued after the first one, until the scope is re-armed.\n",
    "\n",
    "Here we want to trigger on each of the 10 AES rounds, and so we set `multiple_triggers`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "984d1b65-82cc-4049-9091-773ff8067fc1",
   "metadata": {},
   "source": [
    "Let's run the model and print the results. This will take a while; computing SAD in software across a full trace is very slow! (The model is designed for accuracy, not speed: [it is used for verifying the Verilog implementation](https://github.com/newaetech/chipwhisperer-husky-fpga/blob/sad/fpga/sim/test_sad.py).)\n",
    "\n",
    "While this runs, sit back and marvel at how Husky's hardware SAD implementation does this in real-time on incoming power samples, in the blink of an eye."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "810ec427-7f36-4070-8428-c83befc25068",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "sad_model = cw.SADModelWrapper(scope.SAD)\n",
    "sad_model.run(reftrace.wave)\n",
    "print(sad_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57c5a7e2-da9f-4574-bb86-288b7f198bc7",
   "metadata": {},
   "source": [
    "We should have matched on each of the 10 rounds:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "82f3fde2-6242-4ffe-9f71-0777d52a05e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert sad_model.num_triggers == 10"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "164f67cd-c611-4a84-a232-a22e20ccc25e",
   "metadata": {},
   "source": [
    "Now let's see what the SAD scores actually look like.\n",
    "\n",
    "With SAD, the smaller the number, the better the match -- a perfectly-matching trace would have a SAD score of 0.\n",
    "\n",
    "The red horizontal line is our `scope.SAD.threshold` setting: whenever a SAD score equal or less than `scope.SAD.threshold` is found, a trigger is issued.\n",
    "\n",
    "A properly-tuned SAD trigger should have low peaks that are clearly distinct, and `scope.SAD.threshold` roughly halfway between the lowest peaks and the \"rest\". That is what you should see here:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "70124444-655c-441d-bd9e-a574545695bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(width=1800)\n",
    "dat = sad_model.SADS\n",
    "\n",
    "xrange = list(range(len(dat)))\n",
    "p.line(xrange, dat)\n",
    "p.renderers.extend([Span(location=scope.SAD.threshold, dimension='width', line_color='red', line_width=2)])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be74eea5-f78c-4e70-bf7f-141d0279d1a5",
   "metadata": {},
   "source": [
    "We can also visualize the 10 waveform segments that would be captured with these SAD settings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9612644-8f0e-4970-8d81-221231efab42",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.palettes import inferno\n",
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "import itertools\n",
    "\n",
    "SAMPLES = scope.SAD.sad_reference_length\n",
    "\n",
    "numplots = len(sad_model.match_times)\n",
    "xrange = list(range(SAMPLES))\n",
    "p = figure(width=1800)\n",
    "colors = itertools.cycle(inferno(numplots))\n",
    "for i in range(numplots):\n",
    "    offset = sad_model.match_times[i] - scope.SAD.sad_reference_length\n",
    "    p.line(xrange, reftrace.wave[offset:offset+SAMPLES], color=next(colors))\n",
    "\n",
    "p.line(xrange, scope.SAD.reference[:SAMPLES], line_color='grey', line_width=3, line_dash='dotted')\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d403987-dfc2-4a75-95c0-6d7ae47387cd",
   "metadata": {},
   "source": [
    "# Real SAD Triggering\n",
    "\n",
    "Now that we have found good SAD parameters, let's run some actual hardware-based SAD triggering with Husky.\n",
    "\n",
    "`scope.SAD` is already set-up, so we don't need to touch any of its settings; we just need to switch the trigger module from `basic` to `SAD`, and set our `scope.adc` parameters to capture the number of segments and samples that we want.\n",
    "\n",
    "Remember that SAD will be triggering on every AES round, so we set `scope.adc.segments = 10`. The capture duration of each segment must be shorter than an AES round: roughly `scope.adc.trig_count / 10`, which is 3186 (otherwise, you will get a \"segmenting error\").\n",
    "\n",
    "The SAD module triggers at the **end** of the SAD reference -- it can't go back in time to issue the trigger at the start of the reference! But, we can use the `scope.adc.presamples` feature to essentially go back in time and capture the matching power trace.\n",
    "\n",
    "To line up the segments perfectly with the SAD reference, we need to set `scope.adc.presamples` to the length of the SAD reference (`scope.SAD.sad_reference_length`), plus the SAD module's small fixed triggering latency (`scope.SAD.latency`).\n",
    "\n",
    "Finally, since we are using segmenting *and* presamples, `scope.adc.samples` must be a multiple of 3 (this is a limitation of the Husky capture mechanism)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "421f0066-4cc9-455f-a4d9-32ba41fc1a81",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trigger.module = 'SAD'\n",
    "scope.adc.presamples = scope.SAD.sad_reference_length + scope.SAD.latency\n",
    "scope.adc.samples = scope.adc.presamples + 100 # let's capture a bit more than the SAD reference\n",
    "scope.adc.samples -= scope.adc.samples %3 # when using segments with presamples, the number of samples per segment (scope.adc.samples) must be a multiple of 3.\n",
    "scope.adc.segments = 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1279b7a5-a39a-45f5-b2f4-c339daa5cd88",
   "metadata": {},
   "outputs": [],
   "source": [
    "sadtrace = cw.capture_trace(scope, target, bytearray(16), bytearray(16), as_int=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b1e20dc-98e2-4690-8a0b-8633b53c76a7",
   "metadata": {},
   "source": [
    "`scope.SAD.num_triggers_seen` tells us how many times the SAD module triggered; there should have been 10 triggers, one for each AES round:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "be86f3ad-6f2f-4cf1-96c4-c25478dbf5ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert scope.SAD.num_triggers_seen == 10"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcb96b8d-95fd-4114-bd0d-aa81471b96f7",
   "metadata": {},
   "source": [
    "Husky also handily logs the time between successive triggers; let's see what those timestamps are:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d01b5d29-cb59-4829-9e64-43d44240f15a",
   "metadata": {},
   "outputs": [],
   "source": [
    "ttimes = scope.trigger.get_trigger_times()\n",
    "print(ttimes)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57d1c140-d47a-4cea-8a65-b1803f13bb2c",
   "metadata": {},
   "source": [
    "Each round except for the last one takes exactly the same number of ADC clock cycles, which is not surprising; this gives us confidence that our SAD reference works well as an AES round marker.\n",
    "\n",
    "These trigger times should be exactly what we obtained from the model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80f2672b-6831-488c-9137-30b3f39c4a4e",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert sad_model.match_time_deltas == ttimes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9e74938-2fd3-44cb-b852-8ea302b49362",
   "metadata": {},
   "source": [
    "# SAD Explorer\n",
    "\n",
    "To make it easier to explore and understand the many `scope.SAD` parameters, ChipWhisperer has a new interactive \"SAD Explorer\" module.\n",
    "\n",
    "When we launch the SAD Explorer, it will start with the same capture parameters that we have been using.\n",
    "\n",
    "We provide the explorer with the `scope` and `target` objects, the **full** reference trace (not just our chosen `scope.SAD.reference`: this is so that we can experiment with changing `scope.SAD.reference`), the starting index of the SAD reference that we have chosen, and the maximum number of segments that we wish to capture.\n",
    "\n",
    "Note that while `scope.SAD` can work with `scope.adc.bits_per_sample` set to either 8 or 12, `SADEXplorer` requires it to be 8; it also requires the reference trace to be captured as integers (i.e. `cw.capture(as_int=True)`).\n",
    "\n",
    "Push the \"run SAD capture\" button that appears after you run the cell below; the plot should briefly flash yellow, then green, then plot the 10 captured segments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc66ba25-700e-450a-81cb-e5d28ba5a0ac",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "explorer = cw.SADExplorer(scope, target, reftrace.wave, refstart, max_segments=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b094bc2e-b89e-4de6-bb7c-67282982d697",
   "metadata": {},
   "source": [
    "There is a **lot** to play with here. Some basic usage notes:\n",
    "1. The vertical red lines denote the start and end of the SAD reference.\n",
    "2. If the plot flashes green, the capture was successful; if it stays red, the capture failed (look at the resulting messages to find out why).\n",
    "3. The gray area is defined by the SAD reference and `scope.SAD.interval_theshold`. Husky now uses the \"interval matching\" method defined by [Beckers et al.](https://www.esat.kuleuven.be/cosic/publications/article-2626.pdf):\n",
    "    - for each incoming sample, if the incoming waveform sample is within the range of the SAD reference sample +/- `scope.SAD.interval_threshold`, the SAD score remains unchanged; otherwise it is increased by 1\n",
    "    - this effectively splits the old single `scope.SAD.threshold` parameter into *two* separate threshold parameters. While this increases the configuration space, in practice you should find that this makes SAD easier to tune.\n",
    "    - SAD computations always use the most significant 8 bits of each sample (regardless of the `scope.adc.bits_per_sample` setting); consequently the maximum setting for `scope.SAD.interval_threshold` is 255.\n",
    "    - the maximum `scope.SAD.threshold` value is less than `scope.SAD.sad_reference_length` (for FPGA resource optimizations). If you find yourself needing a higher value than what's possible, either:\n",
    "       1. increase `scope.SAD.interval_threshold`;\n",
    "       2. find a better reference;\n",
    "       3. shorten the reference via `enabled_samples` and/or `trigger_sample` (which are explained below).      \n",
    "4. You can turn on legends in the plot and/or in a separate text cell, with or without SW-computed SAD scores for the captured segments. The more of these you turn on, the slower the capture gets. Remember that green is good: it means that the capture was successful and that the plot is still being updated.\n",
    "5. It's possible to exclude arbitrary samples from the SAD computation via the \"excluded samples\" dialog:\n",
    "    - you can list samples to exclude like this: \"1, 10, 20:30\"\n",
    "    - in the Python API, specify these as: `scope.SAD.enabled_samples[1] = False`; `scope.SAD.enabled_samples[10:20] = [False]*10`\n",
    "6. It's also possible to shorten the SAD reference and advance the SAD trigger accordingly by reducing `scope.SAD.trigger_sample` arbitrarily; samples after `scope.SAD.trigger_sample` are excluded from the SAD computation. (Turning off samples via \"excluded samples\" does not advance the trigger.)\n",
    "7. You can increase `scope.adc.samples` and/or \"extra presamples\" to explore areas around the chosen reference. (You can't set `scope.adc.presamples` directly: `SADExplorer` sets it automatically to ensure that the reference trace is fully captured.)\n",
    "8. Use the \"show diff\" option to plot the *absolute difference* between the captured samples and the reference. This can be particularly useful for tuning the thresholds and deciding whether some samples should be excluded.\n",
    "9. You can **double** the length of the SAD reference by turning on `scope.SAD.emode`. More on this below."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f77d55e-11de-46e2-bdff-c1163be21302",
   "metadata": {},
   "source": [
    "### Some ideas of things to try:\n",
    "1. The first round has a few samples at the start that diverge considerably from the reference; exclude those samples and make the thresholds tighter. The \"show diff\" option can be very helpful here.\n",
    "2. Turn on \"emode\" to use a longer reference.\n",
    "3. What happens when the thresholds are too loose, or too many samples are excluded?\n",
    "4. Increase `scope.adc.samples` and `scope.adc.presamples` to hunt around for a better `refstart` nearby (i.e. one which doesn't require samples to be excluded).\n",
    "5. How small can you make `scope.SAD.trigger_sample` and still reliably capture all 10 rounds? *(be sure to check that the trigger times are still good, i.e. that triggers are not occuring ~randomly!)*\n",
    "6. Try to find a totally different reference segment that works well.\n",
    "\n",
    "If you mess things up, you can return to our known good settings with the cell below (run that and then re-run the `SADExplorer` instantiation cell above)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b1af44f-1928-4aad-98ac-066075c260d0",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "refstart = 17372\n",
    "scope.SAD.threshold = 30\n",
    "scope.SAD.interval_threshold = 20\n",
    "scope.SAD.emode = False\n",
    "scope.SAD.always_armed = False\n",
    "scope.SAD.reference = reftrace.wave[refstart:]\n",
    "\n",
    "scope.adc.samples = 300\n",
    "scope.adc.segments = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13ee94d8-1e21-49b4-b639-2d99fc8c3e70",
   "metadata": {},
   "source": [
    "## Extended Mode\n",
    "\n",
    "`scope.SAD.emode` doubles the length of the SAD reference. Hardware SAD is **expensive** (in terms of FPGA resources), so how do we manage to do this? By taking a chance...\n",
    "\n",
    "When `scope.SAD.emode` is `False`, a power trace that \"matches\" the reference as per the SAD threshold values will **always** result in the match being detected and a trigger being issued.\n",
    "\n",
    "When `scope.SAD.emode` is `True`, there is a  *(very very)* small chance that some valid matches are missed.\n",
    "\n",
    "Considering that SAD triggering *in practice* is probabilistic (noisy traces means that SAD triggering may not work 100% of the time), it's reasonable to accept the risk of `emode` missing triggers. If you want to fully understand how and why missed triggers can arise, read on..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d89150bc-7089-485a-b54a-508c11d84e1c",
   "metadata": {},
   "source": [
    "### Extended Mode Details \n",
    "In order for `emode` to miss triggers, there must be some periodicity in the power trace of length `scope.SAD.sad_reference_length / 2` (note that `scope.SAD.sad_reference_length` depends on the `scope.SAD.emode` setting; use the value given when `emode` is `True`).\n",
    "\n",
    "For example, if `scope.SAD.sad_reference_length` is 512, and the power trace has a repeating pattern that has a period of 256 samples, then `emode` may not work very well; in this scenario you would probably get more reliable SAD triggering with `emode` turned off (alternatively, change `scope.clock.adc_mul` if you're able to).\n",
    "\n",
    "Our software SAD model can be used to illustrate how this happens. Let's synthesize a fictional reference and power trace to help illustrate. We'll use one full period of a 512-sample sine wave as a reference:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0aef36e-c926-4f04-a1d9-acbdd4e4ceff",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "sineref = ((np.sin(np.arange(0, np.pi*2, np.pi*2/512))/2 + 0.5)*255).astype(np.uint8)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79d5cf71-fcac-41e4-84b1-d635201af2df",
   "metadata": {},
   "source": [
    "The fictional incoming power trace is made of random samples; we then insert the reference in two locations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7fa7b1c-11b6-4261-8fd9-86a280aff482",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "trace = np.asarray([random.randint(0,255) for _ in range(5000)], dtype=np.uint8)\n",
    "\n",
    "trace[1000:1512] = sineref\n",
    "trace[3000:3512] = sineref"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "857428e1-8807-43b7-b175-617896e8a972",
   "metadata": {},
   "source": [
    "Since the reference is contained exactly, the SAD thresholds can be set to minimal values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6b99976-ccc8-48c7-aa71-746172f455d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.threshold = 2\n",
    "scope.SAD.interval_threshold = 1\n",
    "scope.SAD.reference = sineref\n",
    "scope.SAD.emode = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2f52ad72-1723-40fc-a6c0-cb38af5ba3fd",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "sad_model = cw.SADModelWrapper(scope.SAD)\n",
    "sad_model.run(trace)\n",
    "print(sad_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f578af8e-195e-42bc-9dd3-f9c919661a1e",
   "metadata": {},
   "source": [
    "The model triggers twice, as expected. Because `scope.SAD.emode = True`, the model has a new `uncovered_samples` property which contains two elements.\n",
    "\n",
    "If an incoming trace that matches the reference started at any of the `sad_model.uncovered_samples` indices, the match would be missed.\n",
    "\n",
    "Here you can see that the `uncovered_samples` elements are at exactly the halfway point of the incoming sequences that later do result in a trigger, and that is no coincidence: in order to double the length of the SAD reference that is checked, Husky is unable to consider a potential match that starts at the halfway point.\n",
    "\n",
    "Let's synthesize a special case which **does** result in a missed trigger to illustrate this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eea84d9c-0f84-4aee-b5fe-0bc14402e9ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "trace = np.asarray([random.randint(0,255) for _ in range(5000)], dtype=np.uint8)\n",
    "trace[1000:1256] = sineref[:256]\n",
    "trace[1256:1768] = sineref[:512]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a47b6a5e-b130-42d6-95aa-b576d89dd969",
   "metadata": {},
   "source": [
    "This trace construction contains the first half of the reference at indices [1000:1256], followed immediately by the full reference at indices [1256:1768].\n",
    "\n",
    "Running the model on this trace shows that the full reference is not caught:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dee4a86a-7697-48de-aa65-88d610352eba",
   "metadata": {},
   "outputs": [],
   "source": [
    "sad_model = cw.SADModelWrapper(scope.SAD, catch_emisses=True)\n",
    "sad_model.run(trace)\n",
    "print(sad_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c28af82-b334-431e-9f47-238efeca6760",
   "metadata": {},
   "source": [
    "Running the model with `catch_emisses=True` runs the model twice: once in a \"normal Husky model mode\", and then again in a \"full SAD mode\" which doesn't miss *any* triggers (unlike Husky).\n",
    "\n",
    "As a result we get both the `uncovered_samples` and `missed_triggers` information.\n",
    "\n",
    "Printing the `sad_model` object gives the summarized results; the specific results of the normal Husky model are at `sad_model.sad`, and the results of the full SAD model are at `sad_model.fsad`.\n",
    "\n",
    "You can play around with inserting segments in different ways to get a better feel for the situations which result in missed triggers.\n",
    "\n",
    "The other \"gotcha\" of `emode` to be aware of is that at the halfway point of the full reference, the current SAD score must be less than **half** of `scope.SAD.threshold` in order for a potential match to go ahead.\n",
    "\n",
    "To illustrate, first we run the model with a single sample outside of `scope.SAD.interval_threshold`, which results in a match:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18dc469a-2fff-4752-8ca2-939bbf6121c6",
   "metadata": {},
   "outputs": [],
   "source": [
    "trace = np.asarray([random.randint(0,255) for _ in range(5000)], dtype=np.uint8)\n",
    "trace[1000:1512] = sineref\n",
    "trace[1032] += 2\n",
    "\n",
    "scope.SAD.threshold = 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f59badb-39d2-4a17-9b47-d11a06c36871",
   "metadata": {},
   "outputs": [],
   "source": [
    "sad_model = cw.SADModelWrapper(scope.SAD, catch_emisses=False)\n",
    "sad_model.run(trace)\n",
    "print(sad_model)\n",
    "\n",
    "assert sad_model.num_triggers == 1\n",
    "assert sad_model.sad.match_scores == [1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a76d295-5b43-4c2e-9dc7-f2c7fc54ce02",
   "metadata": {},
   "source": [
    "But if we move two additional samples outside of `scope.SAD.interval_threshold`, no match will occur, even though the number of samples exceeding the threhsold is less than `scope.SAD.threshold`; this happens because all exceeding samples are in the first half of the pattern:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e7cf327e-0676-43aa-a8f3-2ea893d977f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "trace[1040] += 2\n",
    "trace[1050] += 2\n",
    "\n",
    "sad_model.run(trace)\n",
    "print(sad_model)\n",
    "\n",
    "assert sad_model.num_triggers == 0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f8dc019-d06d-4f42-97c0-da28347275c1",
   "metadata": {},
   "source": [
    "The solution is to increase `scope.SAD.threshold` to at least twice the number of offending samples.\n",
    "\n",
    "Because we've changed `scope.SAD`, we need to re-instantiate the model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5432c2f-b2cb-482e-bf96-09089f96a319",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.threshold = 8\n",
    "\n",
    "sad_model = cw.SADModelWrapper(scope.SAD, catch_emisses=False)\n",
    "sad_model.run(trace)\n",
    "print(sad_model)\n",
    "\n",
    "assert sad_model.num_triggers == 1\n",
    "assert sad_model.sad.match_scores == [3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30c3c8c5-1b33-4e68-b5ad-3bcba324d851",
   "metadata": {},
   "source": [
    "Go back to the `SADExplorer` to see how this can happen with real traces: the first AES round is an excellent example of this since it has several diverging samples early on in the reference pattern!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4f44820-72fe-4923-9179-e4ab1e7b66a5",
   "metadata": {},
   "source": [
    "# ECC target\n",
    "\n",
    "Next we show how to tune SAD to work against [micro-ecc](https://github.com/kmackay/micro-ecc).\n",
    "\n",
    "Again we use pre-compiled firmware to have known good settings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d5791930-9c88-4610-a0b3-561ae165f183",
   "metadata": {},
   "outputs": [],
   "source": [
    "cw.program_target(scope, prog, \"../../../firmware/mcu/simpleserial-ecc-notrace/simpleserial-ecc-fwtrigger-{}.hex\".format(PLATFORM))\n",
    "reset_target(scope)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0e6b33e-64e4-49c3-a517-f7e2be2c9384",
   "metadata": {},
   "outputs": [],
   "source": [
    "target.simpleserial_write('i', b'')\n",
    "time.sleep(0.1)\n",
    "print(target.read())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a17aa8ef-2c40-4f9a-96aa-016e3604e32f",
   "metadata": {},
   "outputs": [],
   "source": [
    "TRACES = 'HARDWARE'\n",
    "%run \"../../courses/sca205/ECC_capture.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e2aaaf0-ed15-4bff-a72a-7c7b1c51f6e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trigger.module = 'basic'\n",
    "scope.trigger.triggers = 'tio4'\n",
    "\n",
    "scope.adc.stream_mode = True\n",
    "scope.adc.presamples = 0\n",
    "scope.adc.samples = int(16e6)\n",
    "scope.adc.segments = 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad54d4ac-a204-4e63-aa04-bef52bbe3d60",
   "metadata": {},
   "source": [
    "The micro-ecc target firmware is not constant time; in order to have a known good SAD reference, we stick to these parameters:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a59432a-b9d4-4733-b58f-0771acf6beac",
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0x526a13ac66957d13622a9d872ff9302c47d6393237efaa4c0fc92c08febc5d2c\n",
    "Px = 0xe479bb253840235126427b2cdff9a862601e1577c2abbc274d4b5372a45656ec\n",
    "Py = 0x561fbeb30f276006b91ba1b81df8e3f3edf40f8ea000593b3a622610af02a50"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4bde047-f178-4d2c-a7d6-7470eecd1684",
   "metadata": {},
   "outputs": [],
   "source": [
    "# try different k / Px / Py!\n",
    "#k = random_k()\n",
    "#Px, Py = new_point()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0259f13-1102-423a-b196-f24e3ac1ba5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "reftrace = capture_ecc_trace(k, Px, Py)\n",
    "scope.errors.clear()\n",
    "print(scope.adc.trig_count)\n",
    "assert scope.adc.trig_count == 15788560"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26251306-130e-40bb-8061-da4f8bdd871e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "from holoviews.operation import decimate\n",
    "from holoviews.operation.datashader import datashade\n",
    "hv.extension('bokeh')\n",
    "datashade(hv.Curve(reftrace.wave)).opts(width=2000, height=900)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e60cab41-8a61-43f0-a6e6-2b6ea033321a",
   "metadata": {},
   "source": [
    "The target firmware triggers at the start of each bit being processed by the point multiplication algorithm; let's peak at how long each bit takes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89c3765e-def5-48d6-865a-7c9467d520d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "ttimes = scope.trigger.get_trigger_times()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53619e59-d96d-49e6-aef0-c7590da09959",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ttimes[:10])\n",
    "print('Min: %d' % min(ttimes))\n",
    "print('Max: %d' % max(ttimes))\n",
    "\n",
    "# sanity check:\n",
    "assert min(ttimes) == 105024 and max(ttimes) == 110900"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69073513-4d17-4c4e-b86d-b2cd61f51aae",
   "metadata": {},
   "source": [
    "Finding a good SAD reference for this target is much harder than it was for AES. This, which was found through much trial and error, should work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3fcb5b6-c4cc-4b74-9e97-bb70db49710e",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.emode = True\n",
    "refstart = 1028600"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27dbb44b-fba0-4acd-9f68-510c858175dc",
   "metadata": {},
   "source": [
    "Let's plot a subset of the reference trace that covers about 3 bits somewhere in the middle of the operation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3dd93bb1-ecae-4804-a4d1-b2829d530101",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span\n",
    "\n",
    "start = int(1e6)\n",
    "samples = 300000\n",
    "output_notebook()\n",
    "p = figure(width=1800, tools='pan, box_zoom, hover, reset, save')\n",
    "\n",
    "xrange = list(range(samples))\n",
    "p.line(xrange, reftrace.wave[start:start+samples])\n",
    "\n",
    "p.renderers.extend([Span(location=refstart-start, dimension='height', line_color='black', line_width=1)])\n",
    "p.renderers.extend([Span(location=refstart-start+scope.SAD.sad_reference_length, dimension='height', line_color='black', line_width=1)])\n",
    "\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ac65ff9-224a-4caa-8c41-10dcfbd7c93e",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trigger.module = 'SAD'\n",
    "scope.SAD.threshold = 15\n",
    "scope.SAD.interval_threshold = 20\n",
    "scope.SAD.emode = True\n",
    "scope.SAD.always_armed = False\n",
    "scope.SAD.multiple_triggers = True\n",
    "scope.SAD.reference = reftrace.wave[refstart:]\n",
    "scope.adc.stream_mode = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45cf0b0f-938b-4dee-ba13-432a7b5c7779",
   "metadata": {},
   "source": [
    "Due to Husky's sample storage limitations, you'll have to reduce `scope.adc.segments` to 187 (or less).\n",
    "\n",
    "However, by turning on `scope.SAD.always_armed`, you can still see whether the expected number of SAD matches (255) occur.\n",
    "\n",
    "You can also verify that the range of trigger times is in line with what was observed above.\n",
    "\n",
    "This shows how a custom capture function can be provided to `SADExplorer`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe658803-7cc6-49d0-a175-e66a297a2790",
   "metadata": {},
   "outputs": [],
   "source": [
    "explorer = cw.SADExplorer(scope, target, reftrace.wave, refstart, max_segments=255, capture_function=lambda: capture_ecc_trace(k, Px, Py))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ae23718-06bb-4311-bd62-33bd782841b3",
   "metadata": {},
   "source": [
    "## Things to try:\n",
    "1. Turn on `scope.adc.always_armed` to make sure that 255 matches are seen.\n",
    "2. If you turn off `scope.SAD.emode`, you can capture all 255 segments.\n",
    "3. How few samples are needed to reliably match? (reduce `scope.SAD.trigger_sample`).\n",
    "4. We used a specific k/Px/Py to find our SAD reference. If you change any/all of k/Px/Py, does that reference still work?\n",
    "5. Can you find another suitable SAD reference?\n",
    "6. Can you use this towards an attack against micro-ecc? (see the [sca205](https://github.com/newaetech/chipwhisperer-jupyter/tree/ches2024sad/courses/sca205) series of notebooks to see how such an attack can be done)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3f06d54-128a-4f2a-bdae-3c459ae724c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# to use different k / Px / Py:\n",
    "k = random_k()\n",
    "Px, Py = new_point()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e79f777a-ca22-4fa8-8284-923f368d4dd8",
   "metadata": {
    "tags": []
   },
   "source": [
    "*known good parameters in hidden cell below:*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ab44d62-e40f-49d8-8abf-ae7bb5a45bf9",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "refstart = 1028600\n",
    "\n",
    "# re-acquire reftrace if these changed!\n",
    "k = 0x526a13ac66957d13622a9d872ff9302c47d6393237efaa4c0fc92c08febc5d2c\n",
    "Px = 0xe479bb253840235126427b2cdff9a862601e1577c2abbc274d4b5372a45656ec\n",
    "Py = 0x561fbeb30f276006b91ba1b81df8e3f3edf40f8ea000593b3a622610af02a50\n",
    "\n",
    "scope.SAD.threshold = 15\n",
    "scope.SAD.interval_threshold = 20\n",
    "scope.SAD.emode = True\n",
    "scope.SAD.always_armed = False\n",
    "scope.SAD.reference = reftrace.wave[refstart:]\n",
    "\n",
    "scope.adc.stream_mode = False\n",
    "scope.adc.samples = 450\n",
    "scope.adc.segments = 200"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4111c938-e169-4701-8656-23064953e80f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "a3df208c-38be-47e7-96a4-19abb42fa229",
   "metadata": {},
   "source": [
    "# Next Steps\n",
    "\n",
    "The beauty of SAD is that it can be used with any target. We used a very specific target in this notebook because it's easier to teach SAD with known good parameters... but the next step is for **you** to gain experience with finding good SAD parameters from scratch, so go explore!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae6e5802-8fc3-46cf-bd74-98448fdd03d8",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (venv39)",
   "language": "python",
   "name": "venv39"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
